package com.rightsidetech.http.wechatutil;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.rightsidetech.commom.base.ResponseData;
import com.rightsidetech.commom.enums.CodeIdEnum;
import com.rightsidetech.commom.utils.HttpClient;
import com.rightsidetech.commom.utils.StringUtil;
import com.rightsidetech.http.config.WechatConfig;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.util.*;

@Slf4j
public class WechatUtil {


    public static ResponseData wechatUnifiedorder(WechatConfig wechatConfig, BigDecimal otherPayAmount, String orderNo, int tradeType, String openId, String passbackParams) {
        try {
            int newTxnAmt = otherPayAmount.intValue();
            HttpClient httpClient = new HttpClient(WechatConfig.APP_PAY_URL, 3000, 3000);
            String xml = WechatUtil.genProductArgs(wechatConfig.getAppId(), wechatConfig.getAppId(), wechatConfig.getTitle(), null, wechatConfig.getMchId(), wechatConfig.getApiKey(), newTxnAmt, UUID.randomUUID().toString().replace("-",""), orderNo, wechatConfig.getWechatNotifyUrl(), tradeType, openId, passbackParams, null);
            log.info("微信生成预支付订单：" + xml);
            xml = httpClient.sendxml(xml, "UTF-8");
            if (xml != null && !xml.equals("")) {
                Map<String, Object> map = WechatUtil.getMapFromXML(xml);
                if ("FAIL".equals(map.get("return_code"))) {
                    log.info("微信返回预支付失败订单,orderNo={},map={},apikey={}", orderNo, JSON.toJSONString(map), wechatConfig.getApiKey());
                    return new ResponseData<>(CodeIdEnum.FAILED, map.get("return_msg"));
                } else {
                    if ("SUCCESS".equals(map.get("return_code"))) {
                        if ("SUCCESS".equals(map.get("result_code"))) {
                            log.info("微信返回预支付成功订单,orderNo={},map={},apikey={}", orderNo, JSON.toJSONString(map), wechatConfig.getApiKey());
                            return new ResponseData<>(CodeIdEnum.Success, WechatUtil.getPaySignMap(map, wechatConfig.getApiKey()));
                        } else {
                            log.info("微信返回预支付失败订单,orderNo={},map={},apikey={}", orderNo, JSON.toJSONString(map), wechatConfig.getApiKey());
                            return new ResponseData<>(CodeIdEnum.FAILED, map.get("err_code_des"));
                        }
                    }
                    log.info("微信返回预支付失败订单,orderNo={},map={},apikey={}", orderNo, JSON.toJSONString(map), wechatConfig.getApiKey());
                    return new ResponseData<>(CodeIdEnum.FAILED, map.get("err_code_des"));
                }
            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
        return ResponseData.fail("微信支付请求失败");
    }


    //app使用
    public static ResponseData wechatAppOrder(WechatConfig wechatConfig, BigDecimal otherPayAmount, String orderNo, int tradeType, String openId, String passbackParams) {
        try {
            int newTxnAmt = otherPayAmount.intValue();
            HttpClient httpClient = new HttpClient(WechatConfig.APP_PAY_URL, 3000, 3000);
            String xml = WechatUtil.genProductArgs(wechatConfig.getAppId(), wechatConfig.getWechatAppId(), wechatConfig.getTitle(), null, wechatConfig.getMchId(), wechatConfig.getApiKey(), newTxnAmt, UUID.randomUUID().toString().replace("-",""), orderNo, wechatConfig.getWechatNotifyUrl(), tradeType, openId, passbackParams, null);
            log.info("微信生成预支付订单：" + xml);
            xml = httpClient.sendxml(xml, "UTF-8");
            if (xml != null && !xml.equals("")) {
                Map<String, Object> map = WechatUtil.getMapFromXML(xml);
                if ("FAIL".equals(map.get("return_code"))) {
                    log.info("微信返回预支付失败订单,orderNo={},map={},apikey={}", orderNo, JSON.toJSONString(map), wechatConfig.getApiKey());
                    return new ResponseData<>(CodeIdEnum.FAILED, map.get("return_msg"));
                } else {
                    if ("SUCCESS".equals(map.get("return_code"))) {
                        if ("SUCCESS".equals(map.get("result_code"))) {
                            log.info("微信返回预支付成功订单,orderNo={},map={},apikey={}", orderNo, JSON.toJSONString(map), wechatConfig.getApiKey());
                            return new ResponseData<>(CodeIdEnum.Success, WechatUtil.getPaySignMap(map, wechatConfig.getApiKey()));
                        } else {
                            log.info("微信返回预支付失败订单,orderNo={},map={},apikey={}", orderNo, JSON.toJSONString(map), wechatConfig.getApiKey());
                            return new ResponseData<>(CodeIdEnum.FAILED, map.get("err_code_des"));
                        }
                    }
                    log.info("微信返回预支付失败订单,orderNo={},map={},apikey={}", orderNo, JSON.toJSONString(map), wechatConfig.getApiKey());
                    return new ResponseData<>(CodeIdEnum.FAILED, map.get("err_code_des"));
                }
            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
        return ResponseData.fail("微信支付请求失败");
    }

    /**
     * @param price   金额
     * @param orderId 订单ID
     * @param orderNo 订单号
     * @return string
     * @since 1.0
     */
    public static String  genProductArgs(String appId, String wechatAppId, String title, String spbillCreateIp, String mchId, String apiKey, int price, String orderId, String orderNo,String url,Integer tradeType,String openId, String attach, String sceneInfo) {
        StringBuffer xml = new StringBuffer();
        try {
            xml.append("</xml>");
            List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
            //这里的参数要按签名工具里的顺序来，不然公众号支付签名的时候会报签名错误
            if(tradeType == 4 || tradeType == 1){
                packageParams.add(new NameValuePair("appid", wechatAppId));
            } else{
                packageParams.add(new NameValuePair("appid", appId));
            }
            if(StrUtil.isNotBlank(attach)){
                packageParams.add(new NameValuePair("attach", attach));
            }
            packageParams.add(new NameValuePair("body", title));
            packageParams.add(new NameValuePair("mch_id", mchId));
            packageParams.add(new NameValuePair("nonce_str", orderId));
            packageParams.add(new NameValuePair("notify_url", url));
            if (!StringUtil.isNullorEmpty(openId)) {
                packageParams.add(new NameValuePair("openid", openId));
            }
            packageParams.add(new NameValuePair("out_trade_no", orderNo));
            if(StrUtil.isNotBlank(sceneInfo)){
                packageParams.add(new NameValuePair("scene_info", sceneInfo));
            }
            if(StrUtil.isNotBlank(spbillCreateIp)){
                packageParams.add(new NameValuePair("spbill_create_ip", spbillCreateIp));
            } else{
                packageParams.add(new NameValuePair("spbill_create_ip", "127.0.0.1"));
            }
            packageParams.add(new NameValuePair("total_fee", price + ""));
            if (tradeType==1){
                packageParams.add(new NameValuePair("trade_type", "APP"));
            } else if (tradeType == 2){
                packageParams.add(new NameValuePair("trade_type", "JSAPI"));
            } else if (tradeType == 3){
                packageParams.add(new NameValuePair("trade_type", "MWEB"));
            }else if (tradeType == 4){
                packageParams.add(new NameValuePair("trade_type", "NATIVE"));
            }
            String sign = genPackageSign(packageParams, apiKey);

            //recharge.setSign(sign);

            packageParams.add(new NameValuePair("sign", sign));
            String xmlstring = toXml(packageParams);

            return xmlstring;
        } catch (Exception e) {
            return null;
        }

    }


    public static Map<String, Object> getMapFromXML(String xmlString) throws ParserConfigurationException, IOException, SAXException {

        //这里用Dom的方式解析回包的最主要目的是防止API新增回包字段
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        InputStream is = getStringStream(xmlString);
        Document document = builder.parse(is);

        //获取到document里面的全部结点
        NodeList allNodes = document.getFirstChild().getChildNodes();
        Node node;
        Map<String, Object> map = new HashMap<String, Object>();
        int i = 0;
        while (i < allNodes.getLength()) {
            node = allNodes.item(i);
            if (node instanceof Element) {
                map.put(node.getNodeName(), node.getTextContent());
            }
            i++;
        }
        return map;
    }


    public static Map<String, Object> getPaySignMap(Map<String, Object> map, String apiKey) {
        String timestamp="";
        List<NameValuePair> signParams = new LinkedList<NameValuePair>();
        if (map.get("trade_type").equals("APP")){
            signParams.add(new NameValuePair("appid", (String) map.get("appid")));
            signParams.add(new NameValuePair("noncestr", (String)map.get("nonce_str")));
            signParams.add(new NameValuePair("package", "prepay_id=" + (String)map.get("prepay_id")));
            signParams.add(new NameValuePair("partnerid", (String)map.get("mch_id")));
            signParams.add(new NameValuePair("prepayid", (String)map.get("prepay_id")));
            signParams.add(new NameValuePair("timestamp",timestamp= getCurrentTime10()));
            String sign = genPackageSign(signParams, apiKey);
            map.put("sign",sign);
            map.put("package","prepay_id=" + map.get("prepay_id"));
            map.put("timestamp",timestamp);
            map.put("partnerid",map.get("mch_id"));
            map.remove("mch_id");
        }else{
            signParams.add(new NameValuePair("appId", (String) map.get("appid")));
            signParams.add(new NameValuePair("nonceStr", (String)map.get("nonce_str")));
            signParams.add(new NameValuePair("package", "prepay_id="+map.get("prepay_id")));
            signParams.add(new NameValuePair("signType","MD5"));
            signParams.add(new NameValuePair("timeStamp",timestamp= getCurrentTime10()));
            String sign = genPackageSign(signParams, apiKey);
            map.put("sign",sign);
            map.put("package","prepay_id="+map.get("prepay_id"));
            map.put("timestamp",timestamp);
            map.remove("mch_id");
        }
        return map;
    }

    /**
     * 生成签名
     */
    private static String genPackageSign(List<NameValuePair> params, String apiKey) {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < params.size(); i++) {
            sb.append(params.get(i).getName());
            sb.append('=');
            sb.append(params.get(i).getValue());
            sb.append('&');
        }
        sb.append("key=");
        sb.append(apiKey);
        System.out.println("申请签名:"+sb);
        String packageSign = getMessageDigest(sb.toString().getBytes());
        return packageSign;
    }

    private static String toXml(List<NameValuePair> params) {
        StringBuilder sb = new StringBuilder();
        sb.append("<xml>");
        for (int i = 0; i < params.size(); i++) {
            sb.append("<" + params.get(i).getName() + ">");

            sb.append(params.get(i).getValue());
            sb.append("</" + params.get(i).getName() + ">");
        }
        sb.append("</xml>");

        log.debug(sb.toString());
        String result = "";
        try {
            result = new String(sb.toString().getBytes(), "utf-8");
        } catch (UnsupportedEncodingException e) {
            result = "";
        }
        return result;
    }

    public static InputStream getStringStream(String sInputString) {
        ByteArrayInputStream tInputStringStream = null;
        if (sInputString != null && !sInputString.trim().equals("")) {
            tInputStringStream = new ByteArrayInputStream(sInputString.getBytes());
        }
        return tInputStringStream;
    }

    /**
     * 获取大写mD5串
     *
     * @param buffer byte[]
     * @return string md5
     * @since 1.0
     */
    public final static String getMessageDigest(byte[] buffer) {
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("MD5");
            mdTemp.update(buffer);
            byte[] md = mdTemp.digest();
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str).toUpperCase();
        } catch (Exception e) {
            return null;
        }
    }

    private static String getCurrentTime10() {
        return String.valueOf(new Date().getTime()/1000);
    }

    /**
     * 检验API返回的数据里面的签名是否合法，避免数据在传输的过程中被第三方篡改
     *
     * @param responseString API返回的XML数据字符串
     * @return API签名是否合法
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    public static boolean checkIsSignValidFromResponseString(String responseString, String apiKey) throws ParserConfigurationException, IOException, SAXException {

        Map<String, Object> map = getMapFromXML(responseString);
        log.debug(map.toString());

        String signFromAPIResponse = map.get("sign").toString();
        if (signFromAPIResponse == "" || signFromAPIResponse == null) {
            log.debug("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
            return false;
        }
        log.debug("服务器回包里面的签名是:" + signFromAPIResponse);
        //清掉返回数据对象里面的Sign数据（不能把这个数据也加进去进行签名），然后用签名算法进行签名
        map.put("sign", "");
        //将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
        String signForAPIResponse = WechatUtil.getSign(map, apiKey);

        if (!signForAPIResponse.equals(signFromAPIResponse)) {
            //签名验不过，表示这个API返回的数据有可能已经被篡改了
            log.debug("API返回的数据签名验证不通过，有可能被第三方篡改!!!");
            return false;
        }
        log.debug("恭喜，API返回的数据签名验证通过!!!");
        return true;
    }

    public static String getSign(Map<String, Object> map, String apiKey) {
        ArrayList<String> list = new ArrayList<String>();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getValue() != "") {
                list.add(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            sb.append(arrayToSort[i]);
        }
        String result = sb.toString();
        result += "key=" + apiKey;
        //Util.log("Sign Before MD5:" + result);
        result = getMessageDigest(result.toString().getBytes());
        //Util.log("Sign Result:" + result);
        return result;
    }


    public static String genRefundReq(String appId, String apiKey, String mchId, String transactionID,String outRefundNo,String totalFee,String refundFee,String refundFeeType) {
//        List<org.apache.http.NameValuePair> packageParams = new LinkedList<org.apache.http.NameValuePair>();
        List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
        packageParams.add(new NameValuePair("appid", appId));
        packageParams.add(new NameValuePair("mch_id", mchId));
        packageParams.add(new NameValuePair("nonce_str", getRandomStringByLength(32)));
        packageParams.add(new NameValuePair("out_refund_no",outRefundNo));
        packageParams.add(new NameValuePair("refund_fee", refundFee));
        packageParams.add(new NameValuePair("refund_fee_type",refundFeeType));
        packageParams.add(new NameValuePair("total_fee",totalFee));
        packageParams.add(new NameValuePair("transaction_id",
                transactionID));
        String sign = genPackageSign(packageParams, apiKey);
        packageParams.add(new NameValuePair("sign", sign));
        return toXml(packageParams);
    }

    /**
     * 获取一定长度的随机字符串
     * @param length 指定字符串长度
     * @return 一定长度的字符串
     */
    public static String getRandomStringByLength(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    public static String weichatTransfersToUser(String tradeNo,String openid,String amount,String createIp, String appId, String mchId, String apiKey) {
        List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
        packageParams.add(new NameValuePair("amount", amount));
        packageParams.add(new NameValuePair("check_name", "NO_CHECK"));
        packageParams.add(new NameValuePair("desc", "转账"));
        packageParams.add(new NameValuePair("mch_appid", appId));
        packageParams.add(new NameValuePair("mchid", mchId));
        packageParams.add(new NameValuePair("nonce_str", getRandomStringByLength(32)));
        packageParams.add(new NameValuePair("openid", openid));
        packageParams.add(new NameValuePair("partner_trade_no", tradeNo));
        packageParams.add(new NameValuePair("spbill_create_ip", createIp));

        String sign = genPackageSign(packageParams, apiKey);
        packageParams.add(new NameValuePair("sign", sign));
        return toXml(packageParams);
    }

    public static String getTransferInfoXml(String tradeNo, String appId, String mchId, String apiKey) {
        List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
        packageParams.add(new NameValuePair("appid", appId));
        packageParams.add(new NameValuePair("mch_id", mchId));
        packageParams.add(new NameValuePair("nonce_str", getRandomStringByLength(32)));
        packageParams.add(new NameValuePair("partner_trade_no", tradeNo));
        String sign = genPackageSign(packageParams, apiKey);
        packageParams.add(new NameValuePair("sign", sign));
        return toXml(packageParams);
    }

}
